/*
 * xml_config.cpp
 *
 *  Created on: 2008-10-6
 *      Author: fanliangliang
 */

#include "xml_config.h"
#include "exceptions.h"
#include <iconv.h>
#include <cstring>
#include <cassert>

XmlConfig::XmlConfig(const string& pathname)
:_doc(0)
,_pathname()
,_root()
,_lastPath()
,_exceptionEnabled(true) {
	load(pathname);
}

XmlConfig::XmlConfig()
:_doc(0)
,_pathname()
,_root()
,_lastPath()
,_exceptionEnabled(true) {

}

XmlConfig::~XmlConfig() {
	if (_doc) {
		xmlFreeDoc(_doc);
	}
}

bool XmlConfig::load(const string& pathname) {
	_pathname = pathname;
	_doc = xmlParseFile(_pathname.c_str());
	if (_doc == 0) {
		throwXmlException("Failed to load XML file:" + _pathname);
	}
	return _doc != 0;
}

void XmlConfig::setExceptionEnabled(bool enable) {
	_exceptionEnabled = enable;
}

bool XmlConfig::exceptionEnabled() {
	return _exceptionEnabled;
}

void XmlConfig::setRoot(const string& xpath) {
	if (xpath[0] == '/') {
		_root = xpath;
		size_t size = _root.size();
		if (size > 1 && _root[size - 1] != '/') {
			_root += '/';
		}
	}
}

const string& XmlConfig::root() const {
	return _root;
}

XmlConfig::operator void*() const {
	return _doc;
}

bool XmlConfig::operator !() const {
	return _doc == 0;
}

const string& XmlConfig::pathname() const {
	return _pathname;
}

size_t XmlConfig::count(const string& xpath) {
	size_t n = countImplement(xpath);
	if (n == size_t(-1)) {
		throwXmlException("Failed to evaluate XPath expression:" + lastPath());
	}
	return n;
}

size_t XmlConfig::countf(const char* format, ...) {
	size_t result;
	va_list alist;
  	va_start(alist, format);
  	vcountf(result, format, alist);
	va_end(alist);
	return result;
}

void XmlConfig::throwXmlException(const string& what) {
	if (exceptionEnabled()) {
		throw XmlException(what);
	}
}

void XmlConfig::setLastPath(const string& xpath) {
	_lastPath = xpath;
}

const string& XmlConfig::lastPath() const {
	return _lastPath;
}

xmlXPathObjectPtr XmlConfig::evaluate(const string& xpath) {
	xmlXPathContextPtr context = xmlXPathNewContext(_doc);

	xmlXPathObjectPtr object;

	// transform relative xpath to absolute xpath
	string absolutePath(xpath);
	if (xpath[0] != '/' && !_root.empty()) {
		absolutePath = _root + xpath;
	}

	setLastPath(absolutePath);
	object = xmlXPathEvalExpression((const xmlChar*)absolutePath.c_str(), context);

	if (context != 0) {
		xmlXPathFreeContext(context);
	}

	return object;
}

bool XmlConfig::readStringImplement(string& result, const string& xpath) {
	xmlXPathObjectPtr object = evaluate(xpath);
	if (object == 0) {
		return false;
	}

	xmlNodeSetPtr nodeSet = object->nodesetval;
	if (xmlXPathNodeSetIsEmpty(nodeSet)) {
		xmlXPathFreeObject(object);
		return false;
	}

	xmlChar* value = xmlNodeListGetString(_doc, nodeSet->nodeTab[0]->children, 1);

	const char* cstr = codeConvert((const char*)value, "utf-8", (const char*)_doc->encoding);

	xmlFree(value);
	xmlXPathFreeObject(object);

	if (cstr == 0) {
		return false;
	}

	result.assign(cstr);
	delete [] cstr;
	return true;
}

size_t XmlConfig::countImplement(const string& xpath) {
	xmlXPathObjectPtr object = evaluate(xpath);
	if (object == 0) {
		return size_t(-1);
	}

	size_t n = xmlXPathNodeSetGetLength(object->nodesetval);

	xmlXPathFreeObject(object);
	return n;
}

void XmlConfig::vcountf(size_t& value, const char* format, va_list alist) {
	char xpath[512] = { 0 };
	vsnprintf(xpath, sizeof(xpath), format, alist);
	value = count(xpath);
}

bool XmlConfig::readString(string& result, const string& xpath) {
	bool succeed = readStringImplement(result, xpath);
	if (!succeed) {
		throwXmlException("Failed to evaluate XPath expression:" + lastPath());
	}
	return succeed;
}

const char* codeConvert(const char* in, const char* fromCode, const char* toCode) {
	if (in == 0 || fromCode == 0 || toCode == 0) {
		return 0;
	}

	char* bufout = new char[10240];
	iconv_t cd = iconv_open(toCode, fromCode);
	if (cd == (iconv_t)(-1)) {
		return 0;
	}

	size_t inlen = strlen(in) + 1;
	size_t outlen = 10240;

#if defined(PLATFORM_WIN32)
	const char* sin = in;
#else
	char* sin = (char*)in;
#endif

	char* sout = bufout;
	size_t err = iconv(cd, &sin, &inlen, &sout, &outlen);
	if (err == (size_t)(-1)) {
		return 0;
	}

	iconv_close(cd);
	return bufout;
}

void codeConvert(string* out, const string& in, const string& fromCode, const string& toCode) {
	assert(out != 0);
	const char* result = ::codeConvert(in.c_str(), fromCode.c_str(), toCode.c_str());
	out->assign(result);
	delete [] result;
}
